package com.realshare.frame.util.safety.america;

import com.realshare.frame.util.safety.Base64Utils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * RSA 非对称加密，持有私钥的甲方
 * </p>
 * <p>
 * 工具类依赖包：bcprov-jdk14-123.jar，下载地址http://www.bouncycastle.org/latest_releases.html；
 * </p>
 * <p>
 * 加密可以用公钥，解密用私钥；或者加密用私钥。通常非对称加密是非常消耗资源的，因此可以对大数据用对称加密如：des，aes，而对其对称密钥进行非对称加密，这样既保证了数据的安全，还能保证效率。
 * <p>
 * RSA 很少用来做加密算法使用，一般多数用于数据签名算法中。这是由于 RSA 作为加密算法进行加密处理时，
 * 其所能处理的原文数据量不能超过 （RSA 长度 / 8 - 11），比如：RSA 1024 进行加密运算时，原文的长度不能超过 117 个字节。
 * </p>
 *
 * @author caochuanxi
 * @version 1.0
 * @date 2016-06-23
 */
public class RSAUtils {

    /**
     * 加密算法RSA
     */
    private static final String KEY_ALGORITHM = "RSA";

    /**
     * 签名算法
     * 可以是 md5withrsa 、 sha1withrsa 、 sha256withrsa 、 sha384withrsa 、 sha512withrsa
     */
    private static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    /**
     * 获取公钥的key
     */
    private static final String PUBLIC_KEY = "RSAPublicKey";

    /**
     * 获取私钥的key
     */
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 加密长度
     */
    private static final int KEY_SIZE = 1024;

    /**
     * <p>
     * 生成密钥对(公钥和私钥)
     * </p>
     *
     * @return 返回密钥对
     */
    public static Map<String, String> generateKeyPair() {
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
            keyPairGen.initialize(KEY_SIZE);
            KeyPair keyPair = keyPairGen.generateKeyPair();
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            Map<String, String> keyMap = new HashMap<String, String>();
            keyMap.put(PUBLIC_KEY, Base64Utils.encoder(publicKey.getEncoded()));
            keyMap.put(PRIVATE_KEY, Base64Utils.encoder(privateKey.getEncoded()));
            return keyMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * <p>
     * 用私钥对信息生成数字签名
     * </p>
     *
     * @param data       已加密数据
     * @param privateKey 私钥(BASE64编码)
     * @return 返回签名
     */
    public static String sign(String data, String privateKey) {
        try {
            byte[] dataBytes = Base64Utils.decoder(data);
            byte[] keyBytes = Base64Utils.decoder(privateKey);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(privateK);
            signature.update(dataBytes);
            return Base64Utils.encoder(signature.sign());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * <p>
     * 校验数字签名
     * </p>
     *
     * @param data      已加密数据
     * @param publicKey 公钥(BASE64编码)
     * @param sign      数字签名
     * @return 返回检验结果
     */
    public static boolean verify(String data, String publicKey, String sign) {
        try {
            byte[] dataBytes = Base64Utils.decoder(data);
            byte[] keyBytes = Base64Utils.decoder(publicKey);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            PublicKey publicK = keyFactory.generatePublic(keySpec);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(publicK);
            signature.update(dataBytes);
            return signature.verify(Base64Utils.decoder(sign));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * <P>
     * 私钥解密
     * </p>
     *
     * @param encryptedData 已加密数据
     * @param privateKey    私钥(BASE64编码)
     * @return 返回解密结果
     */

    public static String decryptByPrivateKey(String encryptedData, String privateKey) {
        try {
            byte[] dataBytes = Base64Utils.decoder(encryptedData);
            byte[] keyBytes = Base64Utils.decoder(privateKey);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateK);
            int inputLen = dataBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            int i = 0;
            // 对数据分段解密
            encryptSub(dataBytes, cipher, inputLen, out, offSet, i, MAX_DECRYPT_BLOCK);
            byte[] decryptedData = out.toByteArray();
            out.close();
            return new String(decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * <p>
     * 公钥解密
     * </p>
     *
     * @param encryptedData 已加密数据
     * @param publicKey     公钥(BASE64编码)
     * @return 返回解密结果
     */
    public static String decryptByPublicKey(String encryptedData, String publicKey) {
        try {
            byte[] dataBytes = Base64Utils.decoder(encryptedData);
            byte[] keyBytes = Base64Utils.decoder(publicKey);
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key publicK = keyFactory.generatePublic(x509KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, publicK);
            int inputLen = dataBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            int i = 0;
            // 对数据分段解密
            encryptSub(dataBytes, cipher, inputLen, out, offSet, i, MAX_DECRYPT_BLOCK);
            byte[] decryptedData = out.toByteArray();
            out.close();
            return new String(decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * <p>
     * 公钥加密
     * </p>
     *
     * @param data      源数据
     * @param publicKey 公钥(BASE64编码)
     * @return 返回加密结果
     */
    public static String encryptByPublicKey(String data, String publicKey) {
        try {
            byte[] dataBytes = data.getBytes();
            byte[] keyBytes = Base64Utils.decoder(publicKey);
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key publicK = keyFactory.generatePublic(x509KeySpec);
            // 对数据加密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, publicK);
            int inputLen = dataBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            int i = 0;
            // 对数据分段加密
            encryptSub(dataBytes, cipher, inputLen, out, offSet, i, MAX_ENCRYPT_BLOCK);
            byte[] encryptedData = out.toByteArray();
            out.close();
            return Base64Utils.encoder(encryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    private static void encryptSub(byte[] dataBytes, Cipher cipher, int inputLen, ByteArrayOutputStream out
            , int offSet, int i, int maxEncryptBlock) throws IllegalBlockSizeException, BadPaddingException {
        byte[] cache;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > maxEncryptBlock) {
                cache = cipher.doFinal(dataBytes, offSet, maxEncryptBlock);
            } else {
                cache = cipher.doFinal(dataBytes, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * maxEncryptBlock;
        }
    }

    /**
     * <p>
     * 私钥加密
     * </p>
     *
     * @param data       源数据
     * @param privateKey 私钥(BASE64编码)
     * @return 返回加密结果
     */
    public static String encryptByPrivateKey(String data, String privateKey) {
        try {
            byte[] dataBytes = data.getBytes();
            byte[] keyBytes = Base64Utils.decoder(privateKey);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, privateK);
            int inputLen = dataBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            if (inputLen - offSet > 0) {
                do {
                    if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                        cache = cipher.doFinal(dataBytes, offSet, MAX_ENCRYPT_BLOCK);
                    } else {
                        cache = cipher.doFinal(dataBytes, offSet, inputLen - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    i++;
                    offSet = i * MAX_ENCRYPT_BLOCK;
                } while (inputLen - offSet > 0);
            }
            byte[] encryptedData = out.toByteArray();
            out.close();
            return Base64Utils.encoder(encryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * <p>
     * 获取私钥
     * </p>
     *
     * @param keyMap 密钥对
     * @return 返回私钥
     */
    public static String getPrivateKey(Map<String, String> keyMap) {
        return keyMap.get(PRIVATE_KEY);
    }

    /**
     * <p>
     * 获取公钥
     * </p>
     *
     * @param keyMap 密钥对
     * @return 返回公钥
     */
    public static String getPublicKey(Map<String, String> keyMap) {
        return keyMap.get(PUBLIC_KEY);
    }

    public static void main(String[] args) {
        Date date = new Date();
        Map<String, String> keyMap = RSAUtils.generateKeyPair();
        if (keyMap != null) {
            String source = "一醉汉不慎从三楼掉下，引来路人围观，一警察过来：发生了什么事？醉汉：不清楚，我也是刚到！";
            String publicKey = RSAUtils.getPublicKey(keyMap);
            String privateKey = RSAUtils.getPrivateKey(keyMap);
            System.err.println("公钥：" + publicKey);
            System.err.println("私钥： " + privateKey);
            test(publicKey, privateKey, source);
            testSign(publicKey, privateKey, source);
            System.out.println("加密耗时：" + (new Date().getTime() - date.getTime()) + "毫秒");
        }
    }

    private static void test(String publicKey, String privateKey, String source) {
        System.err.println("公钥加密——私钥解密");
        System.out.println("原文字：" + source);
        System.out.println("公钥：" + publicKey);
        System.out.println("私钥：" + privateKey);
        String encodedData = RSAUtils.encryptByPublicKey(source, publicKey);
        System.out.println("rsa加密后文字：" + encodedData);
        String decodedData = RSAUtils.decryptByPrivateKey(encodedData, privateKey);
        System.out.println("rsa解密后文字: " + decodedData);
    }

    private static void testSign(String publicKey, String privateKey, String source) {
        System.err.println("私钥加密——公钥解密");
        System.out.println("原文字：" + source);
        String encodedData = RSAUtils.encryptByPrivateKey(source, privateKey);
        System.out.println("加密后：" + encodedData);
        String decodedData = RSAUtils.decryptByPublicKey(encodedData, publicKey);
        System.out.println("解密后: " + decodedData);
        System.err.println("私钥签名——公钥验证签名");
        String sign = RSAUtils.sign(encodedData, privateKey);
        System.err.println("签名:" + sign);
        boolean status = RSAUtils.verify(encodedData, publicKey, sign);
        System.err.println("验证结果:" + status);
    }
}
